// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef ZIRCON_SYSTEM_DEV_DISPLAY_INTEL_I915_REGISTERS_PIPE_H_
#define ZIRCON_SYSTEM_DEV_DISPLAY_INTEL_I915_REGISTERS_PIPE_H_

#include <assert.h>
#include <ddk/protocol/display/controller.h>
#include <ddk/protocol/intelgpucore.h>
#include <hwreg/bitfields.h>
#include <zircon/pixelformat.h>

namespace registers {

// Number of pipes that the hardware provides.
static constexpr uint32_t kPipeCount = 3;

enum Pipe { PIPE_A, PIPE_B, PIPE_C, PIPE_INVALID };

static const Pipe kPipes[kPipeCount] = {
    PIPE_A,
    PIPE_B,
    PIPE_C,
};

static constexpr uint32_t kImagePlaneCount = 3;
static constexpr uint32_t kCursorPlane = 2;

// PIPE_SRCSZ
class PipeSourceSize : public hwreg::RegisterBase<PipeSourceSize, uint32_t> {
 public:
  static constexpr uint32_t kBaseAddr = 0x6001c;

  DEF_FIELD(28, 16, horizontal_source_size);
  DEF_FIELD(11, 0, vertical_source_size);
};

// PIPE_BOTTOM_COLOR
class PipeBottomColor : public hwreg::RegisterBase<PipeBottomColor, uint32_t> {
 public:
  static constexpr uint32_t kBaseAddr = 0x70034;

  DEF_BIT(31, gamma_enable);
  DEF_BIT(30, csc_enable);
  DEF_FIELD(29, 20, r);
  DEF_FIELD(19, 10, g);
  DEF_FIELD(9, 0, b);
};

// PLANE_SURF
class PlaneSurface : public hwreg::RegisterBase<PlaneSurface, uint32_t> {
 public:
  static constexpr uint32_t kBaseAddr = 0x7019c;

  // This field omits the lower 12 bits of the address, so the address
  // must be 4k-aligned.
  static constexpr uint32_t kPageShift = 12;
  DEF_FIELD(31, 12, surface_base_addr);
  static constexpr uint32_t kRShiftCount = 12;
  static constexpr uint32_t kLinearAlignment = 256 * 1024;
  static constexpr uint32_t kXTilingAlignment = 256 * 1024;
  static constexpr uint32_t kYTilingAlignment = 1024 * 1024;

  DEF_BIT(3, ring_flip_source);
};

// PLANE_SURFLIVE
class PlaneSurfaceLive : public hwreg::RegisterBase<PlaneSurfaceLive, uint32_t> {
 public:
  static constexpr uint32_t kBaseAddr = 0x701ac;

  // This field omits the lower 12 bits of the address, so the address
  // must be 4k-aligned.
  static constexpr uint32_t kPageShift = 12;
  DEF_FIELD(31, 12, surface_base_addr);
};

// PLANE_STRIDE
class PlaneSurfaceStride : public hwreg::RegisterBase<PlaneSurfaceStride, uint32_t> {
 public:
  static constexpr uint32_t kBaseAddr = 0x70188;

  DEF_FIELD(9, 0, stride);
};

// PLANE_SIZE
class PlaneSurfaceSize : public hwreg::RegisterBase<PlaneSurfaceSize, uint32_t> {
 public:
  static constexpr uint32_t kBaseAddr = 0x70190;

  DEF_FIELD(27, 16, height_minus_1);
  DEF_FIELD(12, 0, width_minus_1);
};

// PLANE_CTL
class PlaneControl : public hwreg::RegisterBase<PlaneControl, uint32_t> {
 public:
  static constexpr uint32_t kBaseAddr = 0x70180;

  DEF_BIT(31, plane_enable);
  DEF_BIT(30, pipe_gamma_enable);
  DEF_BIT(29, remove_yuv_offset);
  DEF_BIT(28, yuv_range_correction_disable);

  DEF_FIELD(27, 24, source_pixel_format);
  static constexpr uint32_t kFormatRgb8888 = 4;

  DEF_BIT(23, pipe_csc_enable);
  DEF_FIELD(22, 21, key_enable);
  DEF_BIT(20, rgb_color_order);
  DEF_BIT(19, plane_yuv_to_rgb_csc_dis);
  DEF_BIT(18, plane_yuv_to_rgb_csc_format);
  DEF_FIELD(17, 16, yuv_422_byte_order);
  DEF_BIT(15, render_decompression);
  DEF_BIT(14, trickle_feed_enable);
  DEF_BIT(13, plane_gamma_disable);

  DEF_FIELD(12, 10, tiled_surface);
  static constexpr uint32_t kLinear = 0;
  static constexpr uint32_t kTilingX = 1;
  static constexpr uint32_t kTilingYLegacy = 4;
  static constexpr uint32_t kTilingYF = 5;

  DEF_BIT(9, async_address_update_enable);
  DEF_FIELD(7, 6, stereo_surface_vblank_mask);
  DEF_FIELD(5, 4, alpha_mode);
  static constexpr uint32_t kAlphaDisable = 0;
  static constexpr uint32_t kAlphaPreMultiply = 2;
  static constexpr uint32_t kAlphaHwMultiply = 3;

  DEF_BIT(3, allow_double_buffer_update_disable);
  DEF_FIELD(1, 0, plane_rotation);
  static constexpr uint32_t kIdentity = 0;
  static constexpr uint32_t k90deg = 1;
  static constexpr uint32_t k180deg = 2;
  static constexpr uint32_t k270deg = 3;
};

// PLANE_BUF_CFG
class PlaneBufCfg : public hwreg::RegisterBase<PlaneBufCfg, uint32_t> {
 public:
  static constexpr uint32_t kBaseAddr = 0x7017c;
  static constexpr uint32_t kBufferCount = 892;

  DEF_FIELD(25, 16, buffer_end);
  DEF_FIELD(9, 0, buffer_start);
};

// PLANE_WM
class PlaneWm : public hwreg::RegisterBase<PlaneWm, uint32_t> {
 public:
  static constexpr uint32_t kBaseAddr = 0x70140;

  DEF_BIT(31, enable);
  DEF_FIELD(18, 14, lines);
  DEF_FIELD(9, 0, blocks);
};

// PLANE_KEYMSK
class PlaneKeyMask : public hwreg::RegisterBase<PlaneKeyMask, uint32_t> {
 public:
  static constexpr uint32_t kBaseAddr = 0x70198;

  DEF_BIT(31, plane_alpha_enable);
};

// PLANE_KEYMAX
class PlaneKeyMax : public hwreg::RegisterBase<PlaneKeyMax, uint32_t> {
 public:
  static constexpr uint32_t kBaseAddr = 0x701a0;

  DEF_FIELD(31, 24, plane_alpha_value);
};

// PLANE_OFFSET
class PlaneOffset : public hwreg::RegisterBase<PlaneOffset, uint32_t> {
 public:
  static constexpr uint32_t kBaseAddr = 0x701a4;

  DEF_FIELD(28, 16, start_y);
  DEF_FIELD(12, 0, start_x);
};

// PLANE_POS
class PlanePosition : public hwreg::RegisterBase<PlanePosition, uint32_t> {
 public:
  static constexpr uint32_t kBaseAddr = 0x7018c;

  DEF_FIELD(28, 16, y_pos);
  DEF_FIELD(12, 0, x_pos);
};

// PS_CTRL
class PipeScalerCtrl : public hwreg::RegisterBase<PipeScalerCtrl, uint32_t> {
 public:
  static constexpr uint32_t kBaseAddr = 0x68180;

  DEF_BIT(31, enable);
  DEF_FIELD(29, 28, mode);
  static constexpr uint32_t kDynamic = 0;
  static constexpr uint32_t k7x5 = 1;

  DEF_FIELD(27, 25, binding);
  static constexpr uint32_t kPipeScaler = 0;
  static constexpr uint32_t kPlane1 = 1;
  static constexpr uint32_t kPlane2 = 2;
  static constexpr uint32_t kPlane3 = 3;

  DEF_FIELD(24, 23, filter_select);
  static constexpr uint32_t kMedium = 0;
  static constexpr uint32_t kEdgeEnhance = 2;
  static constexpr uint32_t kBilienar = 3;

  static constexpr uint32_t kMinSrcSizePx = 8;
  static constexpr uint32_t kMaxSrcWidthPx = 4096;
  static constexpr uint32_t kPipeABScalersAvailable = 2;
  static constexpr uint32_t kPipeCScalersAvailable = 1;
  static constexpr float k7x5MaxRatio = 2.99f;
  static constexpr float kDynamicMaxRatio = 2.99f;
  static constexpr float kDynamicMaxVerticalRatio2049 = 1.99f;
};

// PS_WIN_POS
class PipeScalerWinPosition : public hwreg::RegisterBase<PipeScalerWinPosition, uint32_t> {
 public:
  static constexpr uint32_t kBaseAddr = 0x68170;

  DEF_FIELD(28, 16, x_pos);
  DEF_FIELD(11, 0, y_pos);
};

// PS_WIN_SIZE
class PipeScalerWinSize : public hwreg::RegisterBase<PipeScalerWinSize, uint32_t> {
 public:
  static constexpr uint32_t kBaseAddr = 0x68174;

  DEF_FIELD(28, 16, x_size);
  DEF_FIELD(11, 0, y_size);
};

// DE_PIPE_INTERRUPT
class PipeDeInterrupt : public hwreg::RegisterBase<PipeDeInterrupt, uint32_t> {
 public:
  DEF_BIT(1, vsync);
};

// CUR_BASE
class CursorBase : public hwreg::RegisterBase<CursorBase, uint32_t> {
 public:
  static constexpr uint32_t kBaseAddr = 0x70084;

  DEF_FIELD(31, 12, cursor_base);
  // This field omits the lower 12 bits of the address, so the address
  // must be 4k-aligned.
  static constexpr uint32_t kPageShift = 12;
};

// CUR_CTL
class CursorCtrl : public hwreg::RegisterBase<CursorCtrl, uint32_t> {
 public:
  static constexpr uint32_t kBaseAddr = 0x70080;

  DEF_BIT(24, pipe_csc_enable);
  DEF_FIELD(5, 0, mode_select);
  static constexpr uint32_t kDisabled = 0;
  static constexpr uint32_t kArgb128x128 = 34;
  static constexpr uint32_t kArgb256x256 = 35;
  static constexpr uint32_t kArgb64x64 = 39;
};

// CUR_POS
class CursorPos : public hwreg::RegisterBase<CursorPos, uint32_t> {
 public:
  static constexpr uint32_t kBaseAddr = 0x70088;

  DEF_BIT(31, y_sign);
  DEF_FIELD(27, 16, y_pos);
  DEF_BIT(15, x_sign);
  DEF_FIELD(12, 0, x_pos);
};

// CUR_SURFLIVE
class CursorSurfaceLive : public hwreg::RegisterBase<CursorSurfaceLive, uint32_t> {
 public:
  static constexpr uint32_t kBaseAddr = 0x700ac;

  static constexpr uint32_t kPageShift = 12;
  DEF_FIELD(31, 12, surface_base_addr);
};

// CSC_COEFF
class CscCoeff : public hwreg::RegisterBase<CscCoeff, uint32_t> {
 public:
  static constexpr uint32_t kBaseAddr = 0x49010;

  hwreg::BitfieldRef<uint32_t> coefficient(uint32_t i, uint32_t j) {
    ZX_DEBUG_ASSERT(i < 3 && j < 3);
    uint32_t bit = 16 - ((j % 2) * 16);
    return hwreg::BitfieldRef<uint32_t>(reg_value_ptr(), bit + 15, bit);
  }
};

class CscCoeffFormat : public hwreg::RegisterBase<CscCoeffFormat, uint16_t> {
 public:
  DEF_BIT(15, sign);
  DEF_FIELD(14, 12, exponent);
  static constexpr uint16_t kExponent0125 = 3;
  static constexpr uint16_t kExponent025 = 2;
  static constexpr uint16_t kExponent05 = 1;
  static constexpr uint16_t kExponent1 = 0;
  static constexpr uint16_t kExponent2 = 7;
  static constexpr uint16_t kExponent4 = 6;
  DEF_FIELD(11, 3, mantissa);
};

// CSC_MODE
class CscMode : public hwreg::RegisterBase<CscMode, uint32_t> {
 public:
  static constexpr uint32_t kBaseAddr = 0x49028;
};

// CSC_POSTOFF / CSC_PREOFF
class CscOffset : public hwreg::RegisterBase<CscOffset, uint32_t> {
 public:
  static constexpr uint32_t kPostOffsetBaseAddr = 0x49040;
  static constexpr uint32_t kPreOffsetBaseAddr = 0x49030;

  DEF_BIT(12, sign);
  DEF_FIELD(11, 0, magnitude);
};

// An instance of PipeRegs represents the registers for a particular pipe.
class PipeRegs {
 public:
  static constexpr uint32_t kStatusReg = 0x44400;
  static constexpr uint32_t kMaskReg = 0x44404;
  static constexpr uint32_t kIdentityReg = 0x44408;
  static constexpr uint32_t kEnableReg = 0x4440c;

  PipeRegs(Pipe pipe) : pipe_(pipe) {}

  hwreg::RegisterAddr<registers::PipeSourceSize> PipeSourceSize() {
    return GetReg<registers::PipeSourceSize>();
  }
  hwreg::RegisterAddr<registers::PipeBottomColor> PipeBottomColor() {
    return GetReg<registers::PipeBottomColor>();
  }

  hwreg::RegisterAddr<registers::PlaneSurface> PlaneSurface(int32_t plane_num) {
    return GetPlaneReg<registers::PlaneSurface>(plane_num);
  }
  hwreg::RegisterAddr<registers::PlaneSurfaceLive> PlaneSurfaceLive(int32_t plane_num) {
    return GetPlaneReg<registers::PlaneSurfaceLive>(plane_num);
  }
  hwreg::RegisterAddr<registers::PlaneSurfaceStride> PlaneSurfaceStride(int32_t plane_num) {
    return GetPlaneReg<registers::PlaneSurfaceStride>(plane_num);
  }
  hwreg::RegisterAddr<registers::PlaneSurfaceSize> PlaneSurfaceSize(int32_t plane_num) {
    return GetPlaneReg<registers::PlaneSurfaceSize>(plane_num);
  }
  hwreg::RegisterAddr<registers::PlaneControl> PlaneControl(int32_t plane_num) {
    return GetPlaneReg<registers::PlaneControl>(plane_num);
  }
  hwreg::RegisterAddr<registers::PlaneOffset> PlaneOffset(int32_t plane_num) {
    return GetPlaneReg<registers::PlaneOffset>(plane_num);
  }
  hwreg::RegisterAddr<registers::PlanePosition> PlanePosition(int32_t plane_num) {
    return GetPlaneReg<registers::PlanePosition>(plane_num);
  }
  // 0 == cursor, 1-3 are regular planes
  hwreg::RegisterAddr<registers::PlaneBufCfg> PlaneBufCfg(int plane) {
    return hwreg::RegisterAddr<registers::PlaneBufCfg>(PlaneBufCfg::kBaseAddr + 0x1000 * pipe_ +
                                                       0x100 * plane);
  }

  hwreg::RegisterAddr<registers::PlaneWm> PlaneWatermark(int plane, int wm_num) {
    return hwreg::RegisterAddr<PlaneWm>(PlaneWm::kBaseAddr + 0x1000 * pipe_ + 0x100 * plane +
                                        4 * wm_num);
  }

  hwreg::RegisterAddr<registers::PlaneKeyMask> PlaneKeyMask(int32_t plane_num) {
    return GetPlaneReg<registers::PlaneKeyMask>(plane_num);
  }
  hwreg::RegisterAddr<registers::PlaneKeyMax> PlaneKeyMax(int32_t plane_num) {
    return GetPlaneReg<registers::PlaneKeyMax>(plane_num);
  }

  hwreg::RegisterAddr<registers::PipeScalerCtrl> PipeScalerCtrl(int num) {
    return hwreg::RegisterAddr<registers::PipeScalerCtrl>(PipeScalerCtrl::kBaseAddr +
                                                          0x800 * pipe_ + num * 0x100);
  }

  hwreg::RegisterAddr<registers::PipeScalerWinPosition> PipeScalerWinPosition(int num) {
    return hwreg::RegisterAddr<registers::PipeScalerWinPosition>(PipeScalerWinPosition::kBaseAddr +
                                                                 0x800 * pipe_ + num * 0x100);
  }

  hwreg::RegisterAddr<registers::PipeScalerWinSize> PipeScalerWinSize(int num) {
    return hwreg::RegisterAddr<registers::PipeScalerWinSize>(PipeScalerWinSize::kBaseAddr +
                                                             0x800 * pipe_ + num * 0x100);
  }

  hwreg::RegisterAddr<registers::PipeDeInterrupt> PipeDeInterrupt(uint32_t type) {
    return hwreg::RegisterAddr<registers::PipeDeInterrupt>(type + 0x10 * pipe_);
  }

  hwreg::RegisterAddr<registers::CursorBase> CursorBase() {
    return GetReg<registers::CursorBase>();
  }

  hwreg::RegisterAddr<registers::CursorCtrl> CursorCtrl() {
    return GetReg<registers::CursorCtrl>();
  }

  hwreg::RegisterAddr<registers::CursorPos> CursorPos() { return GetReg<registers::CursorPos>(); }

  hwreg::RegisterAddr<registers::CursorSurfaceLive> CursorSurfaceLive() {
    return GetReg<registers::CursorSurfaceLive>();
  }

  hwreg::RegisterAddr<registers::CscCoeff> CscCoeff(uint32_t i, uint32_t j) {
    ZX_DEBUG_ASSERT(i < 3 && j < 3);
    uint32_t base = registers::CscCoeff::kBaseAddr + 4 * ((i * 2) + (j == 2 ? 1 : 0));
    return GetCscReg<registers::CscCoeff>(base);
  }

  hwreg::RegisterAddr<registers::CscMode> CscMode() {
    return GetCscReg<registers::CscMode>(registers::CscMode::kBaseAddr);
  }

  hwreg::RegisterAddr<registers::CscOffset> CscOffset(bool preoffset, uint32_t component_idx) {
    uint32_t base = (4 * component_idx) + (preoffset ? registers::CscOffset::kPreOffsetBaseAddr
                                                     : registers::CscOffset::kPostOffsetBaseAddr);
    return GetCscReg<registers::CscOffset>(base);
  }

 private:
  template <class RegType>
  hwreg::RegisterAddr<RegType> GetReg() {
    return hwreg::RegisterAddr<RegType>(RegType::kBaseAddr + 0x1000 * pipe_);
  }

  template <class RegType>
  hwreg::RegisterAddr<RegType> GetPlaneReg(int32_t plane) {
    return hwreg::RegisterAddr<RegType>(RegType::kBaseAddr + 0x1000 * pipe_ + 0x100 * plane);
  }

  template <class RegType>
  hwreg::RegisterAddr<RegType> GetCscReg(uint32_t base) {
    return hwreg::RegisterAddr<RegType>(base + 0x100 * pipe_);
  }

  Pipe pipe_;
};

// Struct of registers which arm double buffered registers
typedef struct pipe_arming_regs {
  uint32_t csc_mode;
  uint32_t pipe_bottom_color;
  uint32_t cur_base;
  uint32_t cur_pos;
  uint32_t plane_surf[kImagePlaneCount];
  uint32_t ps_win_sz[2];
} pipe_arming_regs_t;

}  // namespace registers

#endif  // ZIRCON_SYSTEM_DEV_DISPLAY_INTEL_I915_REGISTERS_PIPE_H_
